#include "MapRenderer.h"
#include "Map.h"
#include "../drawing.h"
#include "../shaderManager.h"
#include "../log.h"
#include <math.h>

#include <lightimage.h>

namespace Tmpl {

MapRenderer::MapRenderer(Map* map, int renderSize)
{
	mMap = map;
	mRenderSize = renderSize;

	// Load all textures
	Pixel* buffer = MALLOC64(512 * 512 * map->GetTextureCount(), Pixel);
	for (int i=0; i<map->GetTextureCount(); ++i)
	{
		// Create file handle
		const char* filename = map->GetTexture(i);
		FILE* fp;
		fopen_s(&fp, filename, "rb");
		if(!fp)
		{
			LOG_ERROR("Unable to open file: %s", filename);
			continue;
		}

		// Load the pixels from file
		int w, h, format;
		Pixel* bits = (Pixel*)stbi_load_from_file(fp, &w, &h, &format, STBI_rgb_alpha);
		fclose(fp);
		if(!bits)
		{
			const char* reason = stbi_failure_reason();
			LOG_ERROR("Unable to load Image \"%s\"", filename);
			continue;
		}
		if (w != 512 || h != 512)
		{
			LOG_ERROR("Incorrect image dimensions, must be 512x512 but is \"%dx%d\"", filename, w, h);
			continue;
		}

		// Copy image bits
		for(int y=0; y<h; ++y)
		{
			const Pixel* line = bits + y * w;
			Pixel* dst = buffer + i * 512 * 512 + y * w;
			for(int x=0; x<w; ++x) dst[x] = line[x];
		}
		stbi_image_free(bits);
	}

	// Build opengl Image
	glGenTextures(1, &mTexId);
	glBindTexture(GL_TEXTURE_2D_ARRAY, mTexId);
	//glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 512, 512, map->GetTextureCount(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	//glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
	FREE64(buffer);

	// Load the shaders
	mShader = ShaderManager::LoadShader("Data/Terrain");

	// Generate the VAB
	glGenVertexArrays(1, &mVAB);
	glBindVertexArray(mVAB);

	// Create the VBO
	glGenBuffers(1, &mVBO);
	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	mVboStrideSize = (3+3+2+MAX_RENDER_TEXTURES)*sizeof(float);

	// Set the Pointers
	GLint vertexLoc = mShader->GetAttribLoc("InVertex");
	GLint normalLoc = mShader->GetAttribLoc("InNormal");
	GLint texCoordLoc = mShader->GetAttribLoc("InTexCoord");
	GLint texValueLoc1 = mShader->GetAttribLoc("InTexValue1");
	GLint texValueLoc2 = mShader->GetAttribLoc("InTexValue2");
	GLint texValueLoc3 = mShader->GetAttribLoc("InTexValue3");
	GLint texValueLoc4 = mShader->GetAttribLoc("InTexValue4");
	
	glEnableVertexAttribArray(vertexLoc);
	glEnableVertexAttribArray(normalLoc);
	glEnableVertexAttribArray(texCoordLoc);
	glEnableVertexAttribArray(texValueLoc1);
	glEnableVertexAttribArray(texValueLoc2);
	glEnableVertexAttribArray(texValueLoc3);
	glEnableVertexAttribArray(texValueLoc4);

	glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(0));
	glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*3));
	glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*6));
	glVertexAttribPointer(texValueLoc1, 4, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*8));
	glVertexAttribPointer(texValueLoc2, 4, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*12));
	glVertexAttribPointer(texValueLoc3, 4, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*16));
	glVertexAttribPointer(texValueLoc4, 4, GL_FLOAT, GL_FALSE, mVboStrideSize, BUFFER_OFFSET(sizeof(float)*20));

	// Generate Indices VBO
	mRenderVertexCount = mRenderSize*mRenderSize*2 - mRenderSize;
	glGenBuffers(1, &mVBOIndices);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIndices);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, mRenderVertexCount*sizeof(GLuint), 0, GL_STATIC_DRAW);
	GLuint* ibuffer = (GLuint*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
	int idx = 0;
	for (int y=0; y<mRenderSize-1; ++y)
	{
		for (int x=0; x<mRenderSize; ++x)
		{
			ibuffer[idx++] = y*mRenderSize+x;
			ibuffer[idx++] = (y+1)*mRenderSize+x;
		}
		ibuffer[idx++] = PRIMITIVE_RESTART_ID;
	}
	glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); 

	// Enable triangle strip
	glPrimitiveRestartIndex(PRIMITIVE_RESTART_ID);

	// were done
	glBindVertexArray(0);
}

MapRenderer::~MapRenderer()
{
	glDeleteBuffers(1, &mVBO);
	glDeleteBuffers(1, &mVBOIndices);
	glDeleteBuffers(1, &mVAB);
	glDeleteTextures(1, &mTexId);
}
void MapRenderer::Update(Vec3f playerPos, Vec3f facing)
{
	// Resolve Player Cell
	int playerX = (int)(playerPos.x);
	int playerY = (int)(playerPos.z);
	int renderSize2 = mRenderSize/2;
	int mapSize = mMap->GetSize();

	// Resolve Visibility
	Vec2f fFacing = Vec2f(-facing.x, facing.z).Normalized();
	Vec2f fOffset = Vec2f(float(-renderSize2), float(-renderSize2)) + fFacing * (float(mRenderSize) * 0.5f - 2.0f);
	int offsetX = int(fOffset.x);
	int offsetY = int(fOffset.y);

	// Bind VBO
	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	glBufferData(GL_ARRAY_BUFFER, mRenderSize*mRenderSize*mVboStrideSize, 0, GL_STREAM_DRAW);
	float* fbuffer = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

	// Update vertices
	int idx = 0;
	mRenderTextureCount = 0;
	for (int i=0; i<MAX_RENDER_TEXTURES; ++i) mRenderTextures[i] = 255;
	for (int y=0; y<mRenderSize; ++y)
	for (int x=0; x<mRenderSize; ++x)
	{
		// Get Map Data
		int mapX = offsetX + playerX + x;
		int mapY = offsetY + playerY + y;
		MapPos* m = mMap->GetMapPos(mapX, mapY);
		if (!m)
		{
			// pos
			fbuffer[idx++] = 0.0f;
			fbuffer[idx++] = 0.0f;
			fbuffer[idx++] = 0.0f;

			// normal
			fbuffer[idx++] = 0.0f;
			fbuffer[idx++] = 1.0f;
			fbuffer[idx++] = 0.0f;

			// uv
			fbuffer[idx++] = 0.0f;
			fbuffer[idx++] = 0.0f;

			// tex
			for (int i=0; i<MAX_RENDER_TEXTURES; ++i) fbuffer[idx++] = 0.0f;
		}
		else
		{
			// Update Pos
			fbuffer[idx++] = float(offsetX + x) - fmodf(playerPos.x, 1.0f);
			fbuffer[idx++] = m->height - playerPos.y;
			fbuffer[idx++] = float(offsetY + y) - fmodf(playerPos.z, 1.0f);

			// Update Normal
			Vec3f n = m->GetNormal();
			fbuffer[idx++] = n.x;
			fbuffer[idx++] = n.y;
			fbuffer[idx++] = n.z;

			// Update UV
			fbuffer[idx++] = float(mapX);// - float(abs(x-offsetX))*0.01f;
			fbuffer[idx++] = float(mapY);// - float(abs(y-offsetY))*0.01f;

			// Update Textures
			for (int i=0; i<MAX_RENDER_TEXTURES; ++i) fbuffer[idx + i] = 0.0f;
			for (int i=0; i<MAX_TEXTURES_PER_POS; ++i)
			{
				MapPosTexture& t = m->textures[i];
				if (t.texId < 255)
				{
					// is this texture already in the render textures array?
					char arrayPos = -1;
					for (int j=0; j<mRenderTextureCount; ++j)
					{
						if (mRenderTextures[j] == t.texId)
						{
							arrayPos = j;
							break;
						}
					}
					if (arrayPos == -1)
					{
						arrayPos = mRenderTextureCount;
						mRenderTextures[mRenderTextureCount++] = t.texId;
					}

					// Now update this vertex
					fbuffer[idx + arrayPos] = float(t.value) / 255.0f;
				}
			}
			idx += MAX_RENDER_TEXTURES;
		}
	}
	glUnmapBuffer(GL_ARRAY_BUFFER); 
}
void MapRenderer::Render(const Matrix4x4f& projection, const Matrix4x4f& modelView)
{     
	glEnable(GL_CULL_FACE);
	glEnable(GL_PRIMITIVE_RESTART);
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);

	glCullFace(GL_BACK);
	//glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

	char buffer[64];
	mShader->Use();
	mShader->Uniform1i("TerrainTex", 0);
	for (int i=0; i<MAX_RENDER_TEXTURES; ++i)
	{
		sprintf_s(buffer, 64, "TextureIndexes[%d]", i);
		mShader->Uniform1f(buffer, mRenderTextures[i]);
	}
	mShader->UniformMatrix4x4("matProjModelView", projection * modelView);
	mShader->UniformMatrix4x4("matModelView", modelView);

	glBindVertexArray(mVAB);

	glBindTexture(GL_TEXTURE_2D_ARRAY, mTexId);
	glDrawElements(GL_TRIANGLE_STRIP, mRenderVertexCount, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);

	mShader->UnUse();

	//glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

	glDisable(GL_PRIMITIVE_RESTART);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
}


}